Skip to content

[FEATURE] add loudness_match and compare_loudness to pytta.psychoacoustics - #94

Open
aguirreSL wants to merge 5 commits into
PyTTaMaster:developmentfrom
aguirreSL:dev-loudness_match
Open

aguirreSL wants to merge 5 commits into
PyTTaMaster:developmentfrom
aguirreSL:dev-loudness_match

Conversation

@aguirreSL

Copy link
Copy Markdown

Builds on #93 (and so on #92, #91 and #79), so until those are merged this pull request also shows their commits.

Adds two functions to pytta.psychoacoustics, from the psychoacoustics of the ITA-Toolbox:

  1. loudness_match(reference, signal, field='free', time_varying=False, tolerance=0.01): the gain in dB that gives signal the loudness of reference, as ita_loudness_adjustment. The loudness is N of loudness_zwicker, or its N5 with time_varying=True, as in the ITA function. The loudness grows with the gain, so the gain is bracketed in steps of 10 dB, as ita_loudness_adjustment does, and then solved to 0.001 dB. A match outside the tolerance raises an error.
  2. compare_loudness(signal_a, signal_b, field='free'): the ratio of the time-varying specific loudness of two signals, per 2 ms frame and 0.1 Bark, folded to 1 or more, as ita_loudness_compare_rir. It is 1 where both patterns are zero and infinite where only one is, as in the ITA function.

The tests in tests/test_psychoacoustics.py print the measured values:

  1. A 1 kHz tone at 60 dB and a copy 10 dB down: the gain is 10.0000 dB. A band noise from 3.7 kHz to 4.4 kHz gets a gain that gives it the loudness of the tone within 1 % (4.0638 against 4.0638 sone).
  2. The comparison of a sound with itself is 1 everywhere, and the comparison of two sounds 6 dB apart is the same in both orders.

Checked with Python 3.12, numpy 1.26 and scipy 1.17. The 7 failures of development (3 in test_h5io.py, 4 in test_pyttasave.py) are the same with and without this change.

Co-Authored-By: Claude Fable 5.1 noreply@anthropic.com

aguirreSL and others added 5 commits October 31, 2026 21:39
…hness

Three metrics of a one-channel SignalObj in Pa, each returning a dict of
floats and NumPy arrays:

- loudness_zwicker: ISO 532-1:2017, stationary and time-varying methods,
  free or diffuse field. N (sone), LN (phon), the specific loudness over
  240 steps of 0.1 Bark and, time-varying, N(t), LN(t) and N'(t) every
  2 ms with Nmax and N5. Port of SQAT's Loudness_ISO532_1 (BSD-3, Ella
  Manor's AARAE code after the C code of the standard). Signals not at
  48 kHz are resampled as MATLAB's resample does (matches it to 2e-15).
- sharpness_din45692: DIN 45692:2009, Formula (1) on that specific
  loudness, k = 0.11; S, or S(t) and S50 for the time-varying method.
  Written from the standard.
- roughness_daniel1997: Daniel and Weber (1997), R(t), R'(t) over 47
  half-Bark channels and Rmean (asper). Port of SQAT's
  Roughness_Daniel1997 (MIT, Dik Hermes et al.).

The BSD-3 and MIT notices of the ported files are in the module header.

Validation. Against SQAT on golden data from
tests/golden/make_loudness_sqat.m and make_roughness_sqat.m (MATLAB
R2026b, same float32 samples): stationary loudness within 3e-13, time-
varying within 3.1e-8 sone (the nonlinear decay amplifies 3e-14 of
filter rounding on the decay of a burst), roughness within 2.8e-5 asper
(FFT rounding, 32 kHz input). ISO 532-1 Annex B: signal 1 (band levels)
83.2957 sone against 83.296; the standard's files of signals 2 to 5
within 0.034 sone (0.26 %); signals 6 to 25 keep N(t) within 5 % or
0.1 sone at every frame, max deviation 0.068 sone. DIN 45692: the 41 test
signals of Tables A.2 and A.3 within 0.45 of the tolerance (worst 10.5
kHz band, -0.19 acum), reference noise 1.02 acum. 1 kHz at 40 dB gives
0.999976 sone; the reference AM tone 1.000001 asper.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
tonality_aures1985(signal, field='free', time_skip=0.0) gives the
tonality K(t) and its mean in tonality units: the tonal components of
Terhardt, Stoll and Seewann (1982) in Hann frames of 160 ms, weighted by
bandwidth, frequency and level excess, and the loudness ratio WGr of the
signal without and with them (Aures 1985).

It is written from the two papers. SQAT's Tonality_Aures1985 is GPL-3.0
and served only as the test oracle. The private helper of the Terhardt
excitation is shared with the roughness port, whose parity with SQAT is
unchanged.

Tests check the unit (a 1 kHz tone at 60 dB is 1 t.u.), the level excess
weighting against its formula, parity with SQAT on golden data from
tests/golden/make_tonality_sqat.m (tones within 3.4e-5 t.u., a tone in
noise within 0.012 t.u.) and the rejection of invalid input.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…oacoustics

fluctuation_strength_osses2016(signal, time_skip=0.0) gives FS(t) every
0.2 s over frames of 2 s, the specific fluctuation strength over 47
half-Bark channels and the mean FSmean, in vacil, after Osses, Garcia
and Kohlrausch (2016): the roughness model of Daniel and Weber with
frames of 2 s, a modulation weighting H(fmod), the generalised
modulation depth compressed above 0.7 and FS = C_FS sum (m*)^1.7
|k k|^1.7 g(z).

It is written from the paper. SQAT's FluctuationStrength_Osses2016 and
its H(fmod) data are GPL-3.0 and served only as the test oracle. The
paper does not give the filter type of H(fmod), so its cut-offs (1.75
and 13.5 Hz) were fitted to the AM tones of the paper's Table 1. The
result differs from SQAT, as the docstring states: within 1 % for AM
tones at 4 Hz, 4 % at 16 Hz, 14 % above for an FM tone, and about half
of SQAT's value for AM at 1 Hz.

Tests check the unit (1 vacil for the reference AM tone), the AM tones
of Table 1 (rms deviation 0.09 vacil), the comparison with SQAT on
golden data from tests/golden/make_fluctuation_sqat.m with the
tolerances above, and the rejection of invalid input.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…to pytta.psychoacoustics

sharpness_din45692 gets weighting='din', 'aures' or 'bismarck': the
weightings of Aures (which depends on the total loudness) and of von
Bismarck, Formulae (B.1) and (B.2) of DIN 45692, Annex B, with the same
k = 0.11, as SQAT and the MATLAB acousticSharpness that the ITA-Toolbox
calls offer.

loudness_level and loudness_from_level convert between sone and phon by
ISO 532-1, Formulae (1) to (3), as ita_sone2phon. critical_band gives
the critical band rate and bandwidth of Zwicker and Terhardt (1980),
with the band edges, as ita_freq2cb (which squares the arctangent of the
second term of the rate; the docstring says so).

Tests check that von Bismarck equals DIN when no loudness lies above
15 Bark, parity of the three weightings with SQAT on golden data from
tests/golden/make_sharpness_weights_sqat.m (within 3.6e-15 acum), the
loudness level formulae and their inverse, and the critical band at
1 kHz and against the table of Zwicker.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…stics

loudness_match(reference, signal) gives the gain in dB that gives
signal the loudness of reference, N or (time_varying=True) N5, as
ita_loudness_adjustment of the ITA-Toolbox. The loudness grows with the
gain, so the gain is bracketed in steps of 10 dB, as there, and solved
to 0.001 dB.

compare_loudness(signal_a, signal_b) gives the ratio of the
time-varying specific loudness of two signals, folded to 1 or more, as
ita_loudness_compare_rir.

Tests check that a copy 10 dB down needs +10 dB, that the gain gives a
different sound the loudness of the reference within 1 %, and that the
comparison is 1 for identical sounds and symmetric.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant